package ra_interface;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Vector;


public class Interface {
	private SSLConnection ssl;
	private BufferedReader stdin = null;
	private boolean isWork = true;
	private Vector<DeIdentifiedHealthRecord> hr = null;
	private String id = null;

	public Interface(String ip, int port){
		ssl = new SSLConnection(ip, port);
		InputStream inputstream = System.in;
		InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
		stdin = new BufferedReader(inputstreamreader);
	}


	public void run()
	{
		try {
			while(isWork){
				if (id == null) {

					System.out.println("--Research Aggregator--");
					System.out.println(" [please log in]");

					System.out.println(
							" 1. Log In\n" +
							" 2. Create New Account\n" +
					" 0. Exit");				

					System.out.print(">");

					String cmd = stdin.readLine();

					if (cmd.equals("0")){
						ssl.writeLine(new Message().setMessage("logoff").toXml());
						break;
					}
					else if (cmd.equals("1") && id == null ){
						logIn();
					}
					else if (cmd.equals("2") && id == null ){
						createNewAccount();
					}
					else System.out.println("invalid input");
				}
				else if (hr == null){
					System.out.println("--Research Aggregator--");
					System.out.println(" [welcome " + id + "]");
					System.out.println(
							" 1. Request permission\n" + 
							" 2. view health records\n" +
					" 0. Exit");
					System.out.print(">");

					String cmd = stdin.readLine();

					if (cmd.equals("0")){
						break;
					}
					else if (cmd.equals("1")){
						requestPermission();
					}
					else if (cmd.equals("2")) {
						searchHealthRecord();
					}
					else System.out.println("invalid input");
				}
				else{ //we have health records now
					System.out.println("--Research Aggregator--");
					System.out.println(" welcome [" + id + "]");

					for (int i = 0; i < hr.size() ; i++){
						System.out.println( " " + i + ". anonymous record");
					}

					System.out.println(" q. Back");
					System.out.print(">");

					String cmd = stdin.readLine();
					if (cmd.equals("q")) {
						hr = null;
					}
					else if (Integer.valueOf(cmd)!= null) {
						int cmd_int = Integer.valueOf(cmd);
						if ( 0 <= cmd_int && cmd_int < hr.size() ){
							hr.get(cmd_int).print();
							while (true) {
								System.out.println(" q. Back");
								System.out.print(">");
								cmd = stdin.readLine();
								if (cmd.equals("q")) break;
								else System.out.println("invalid input");
							}
						}
					}
					else System.out.println("invalid input");
				}			
			}			
		} catch (Exception e){

			e.printStackTrace();

		}


	}

	private void requestPermission() {

		try {
			String cmd, lower, upper;
			CapabilityList cl = new CapabilityList();

			System.out.println("\n-Request Permission-");
			System.out.println(" (we assumed that administrator immediately authorize all requests)\n");

			while (true) {
				System.out.println("enter weight constraints (all/range/none)");
				System.out.print(">");
				cmd = stdin.readLine();
				if (cmd.equals("all")) {
					cl.addCapability("weight", "all", "0.0", "100000.0");
					break;
				}
				else if (cmd.equals("range")) {
					System.out.println("enter lower bound");
					System.out.print(">");
					lower = stdin.readLine();
					System.out.println("enter upper bound");
					System.out.print(">");
					upper = stdin.readLine();
					cl.addCapability("weight", "range", lower, upper);
				}
				else if (cmd.equals("none")) break;
				else System.out.println("invalid input");
			}

			while (true) {
				System.out.println("enter height constraints (all/range/none)");
				System.out.print(">");
				cmd = stdin.readLine();
				if (cmd.equals("all")) {
					cl.addCapability("height", "all", "0.0", "100000.0");
					break;
				}
				else if (cmd.equals("range")) {
					System.out.println("enter lower bound");
					System.out.print(">");
					lower = stdin.readLine();
					System.out.println("enter upper bound");
					System.out.print(">");
					upper = stdin.readLine();
					cl.addCapability("height", "range", lower, upper);
				}
				else if (cmd.equals("none")) break;
				else System.out.println("invalid input");
			}

			while (true) {
				System.out.println("enter age constraints (all/range/none)");
				System.out.print(">");
				cmd = stdin.readLine();
				if (cmd.equals("all")) {
					cl.addCapability("age", "all", "0", "10000");
					break;
				}
				else if (cmd.equals("range")) {
					System.out.println("enter lower bound");
					System.out.print(">");
					lower = stdin.readLine();
					System.out.println("enter upper bound");
					System.out.print(">");
					upper = stdin.readLine();
					cl.addCapability("age", "range", lower, upper);
				}
				else if (cmd.equals("none")) break;
				else System.out.println("invalid input");
			}

			while (true) {
				System.out.println("enter a gender constraint (all/male/female)");
				System.out.print(">");
				cmd = stdin.readLine();
				if (cmd.equals("all") || cmd.equals("male") || cmd.equals("female")) {
					cl.addCapability("gender", cmd, null, null);
					break;
				}
				else if (cmd.equals("none")) break;
				else System.out.println("invalid input");
			}

			while (true) {
				System.out.println("enter a city constraint (all/certain city)");
				System.out.print(">");
				cmd = stdin.readLine();
				if (!cmd.equals("")) {
					cl.addCapability("city", cmd, null, null);
					break;
				}
				else if (cmd.equals("none")) break;
				else System.out.println("invalid input");
			}

			while (true) {
				System.out.println("enter a state constraint (all/certain state)");
				System.out.print(">");
				cmd = stdin.readLine();
				if (!cmd.equals("")) {
					cl.addCapability("state", cmd, null, null);
					break;
				}
				else if (cmd.equals("none")) break;
				else System.out.println("invalid input");
			}

			while (true) {
				System.out.println("enter a medical record constraint (yes/no)");
				System.out.print(">");
				cmd = stdin.readLine();
				if (cmd.equals("yes") || cmd.equals("no")) {
					if (cmd.equals("yes")) cl.addCapability("medical", "yes", null, null);
					break;
				}
				else System.out.println("invalid input");
			}
			System.out.println("cl = " + cl.toXml());

			ssl.writeLine(new Message().setMessage("requestPermission")
					.setIdentity(id, null, null, cl.toXml()).toXml());
			Message message = new Message(ssl.readLine());
			if (message.getMessage().equals("accepted")) {
				message = new Message(ssl.readLine());
				if (message.getMessage().equals("updated")) {
					System.out.println("new permission authorized");
				}
				else System.out.println("permission denied");
			}
			else {
				System.out.println("server does not recognized you");
				isWork = false;
			}
		}
		catch (IOException e) {
			e.printStackTrace();
		}
	}


	public void searchHealthRecord(){
		hr = new Vector<DeIdentifiedHealthRecord>();

		try {
			ssl.writeLine(new Message().setMessage("requestRecords").toXml());
			Message message = new Message(ssl.readLine());
			if (message.getMessage().equals("accepted")) {
				message = new Message(ssl.readLine());
				while (!message.getMessage().equals("end")) {
					hr.add(new DeIdentifiedHealthRecord(message.getHealthRecordAttribute("public")));
					message = new Message(ssl.readLine());
				}
				System.out.println("all list received");
			}
			else {
				System.out.println("server does not recognize you");
				isWork = false;
			}

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}


	public boolean logIn() {
		String id;
		String pass;

		try {
			ssl.connect();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			System.out.println("check your internet connection or HISP agent is currently unavailable");
			return false;
		}

		try {
			System.out.print("Id: ");
			id = stdin.readLine();

			if (System.console() == null) {
				System.out.print("Password: ");
				pass = stdin.readLine();
			}
			else {
				pass = new String(System.console().readPassword("Password: "));
			}			

			ssl.writeLine(new Message().setMessage("logIn")
					.setIdentity(id, pass, null, null).toXml());

			Message message = new Message(ssl.readLine());

			if (message.getMessage().equals("authorized")) {
				this.id = id;
			}
			else {
				ssl.close();
			}
			//TODO Login
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return true;
	}

	public void createNewAccount() {
		String id;
		String password;
		String salt;
		try {
			ssl.connect();
		} catch (Exception e){ 
			System.out.println("check your internet connection or Research Aggregator is currently unavailable");
			return;
		}

		try {
			System.out.println("\n-Create New Account-");
			System.out.println("please enter your email address (this will be your id, max 50 characters).");
			System.out.print(">");
			id = stdin.readLine();

			while (true) {
				System.out.println("please enter your password");
				if (System.console() == null) {
					System.out.print(">");
					password = stdin.readLine();
				}
				else {
					password = new String(System.console().readPassword(">"));
				}

				System.out.println(password);
				if (password.length() >= 8) {
					System.out.println("please confirm your password.");
					if (System.console() == null) {
						System.out.print(">");
						if (password.equals(stdin.readLine())) break;
						else {
							System.out.println("the passwords does not match.");
						}
					}
					else {
						if (password.equals(new String(System.console().readPassword(">")))) break;
						else {
							System.out.println("the passwords does not match.");
						}
					}

					//TODO add more constraints to have more secure password
					System.out.println(password.length());
				}
				else {
					System.out.println("your password is too short");
				}
			}		

			//hash password
			SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
			byte[] bSalt = new byte[8];
			random.nextBytes(bSalt);

			String hashedPassword = bytes2String(getHash(password, bSalt));
			String sSalt = bytes2String(bSalt);

			ssl.writeLine(new Message().setMessage("newAccount")
					.setIdentity(id, hashedPassword, sSalt, null).toXml());			

			Message message = new Message(ssl.readLine());
			if (message.getMessage().equals("created")) {
				System.out.println("your account is created");
				this.id = id; 
			}
			else {
				System.out.println("account creation failed, the same id exists");
				id = null;
				ssl.close();
			}
		} catch (Exception e){
			e.printStackTrace();
		}
	}



	private byte[] getHash(String string, byte[] salt) {		
		MessageDigest mda;
		try {
			mda = MessageDigest.getInstance("SHA-512");
			mda.reset();
			mda.update(salt);
			byte[] digest = mda.digest(string.getBytes());
			for (int i = 0; i < 1000; i++) {
				mda.reset();
				digest = mda.digest(digest);
			}

			return digest;

		} catch (NoSuchAlgorithmException e) {
			return null;
		} 
	}

	/*
	 * http://stackoverflow.com/questions/521101/using-sha1-and-rsa-with-java-security-signature-vs-messagedigest-and-cipher
	 */
	private String bytes2String(byte[] bytes) {
		StringBuilder string = new StringBuilder();
		for (byte b: bytes) {
			String hexString = Integer.toHexString(0x00FF & b);
			string.append(hexString.length() == 1 ? "0" + hexString : hexString);
		}
		return string.toString();
	}

	/*
	 * http://stackoverflow.com/questions/140131/convert-a-string-representation-of-a-hex-dump-to-a-byte-array-using-java
	 */
	public static byte[] string2Bytes(String s) {
		int len = s.length();
		byte[] data = new byte[len / 2];
		for (int i = 0; i < len; i += 2) {
			data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
					+ Character.digit(s.charAt(i+1), 16));
		}
		return data;
	}

}
